package io.gitee.dtdage.app.boot.starter.web.security.service.impl;

import io.gitee.dtdage.app.boot.starter.web.security.context.AccessToken;
import io.gitee.dtdage.app.boot.starter.web.security.context.Principal;
import io.gitee.dtdage.app.boot.starter.web.security.context.SecurityContext;
import io.gitee.dtdage.app.boot.starter.web.security.exception.AuthenticationException;
import io.gitee.dtdage.app.boot.starter.web.security.service.AccessTokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Redis令牌接口实现类
 *
 * @author WFT
 * @since 2024/4/15
 */
@Service
@RequiredArgsConstructor
public class RedisAccessTokenServiceImpl implements AccessTokenService {

    @Override
    public String getId() {
        return "redis";
    }

    private final static int VALIDITY = 60 * 60 * 24;

    private final RedisTemplate<String, Object> template;

    private String getCacheName(Serializable clientId, String token) {
        return String.format("online_user:%s:%s", clientId, token);
    }

    @Override
    public Principal getPrincipal(SecurityContext context) throws AuthenticationException {
        //  获取缓存名称
        String cacheName = this.getCacheName(context.getClientId(), context.getToken());
        //  从缓存中获取用户主体信息
        Principal principal = (Principal) this.template.opsForValue().get(cacheName);
        //  当无法从缓存中获取到用户主体,表示当前的访问令牌已超时,抛出407异常
        if (null == principal) {
            throw new AuthenticationException();
        }
        //  为访问令牌续期
        this.template.expire(cacheName, VALIDITY, TimeUnit.SECONDS);
        return principal;
    }

    @Override
    public <T extends Principal> AccessToken generate(T principal, Serializable clientId) {
        //  生成Token
        String token = UUID.randomUUID().toString();
        //  将主体信息保存到Redis中
        this.template.opsForValue().set(this.getCacheName(clientId, token), principal, VALIDITY, TimeUnit.SECONDS);
        //  生成访问令牌
        return new AccessToken(token, principal, VALIDITY);
    }

}
